/*
 * main.cc
 *
 *  Created on: 2009-11-30
 *      Author: liheyuan
 *    Describe: epoll实现阻塞模式服务器(Echo服务器)
 *
 *   Last Date: 2009-11-30
 *   CopyRight: 2009 @ ICT LiHeyuan
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <errno.h>
#include <errno.h>
#include <sys/resource.h>

#define EPOLL_SIZE 10
#define EVENT_ARR 20
#define BACK_QUEUE 10
#define PORT 18001
#define BUF_SIZE 16

void setnonblocking(int sockFd) {
    int opt;

    //获取sock原来的flag
    opt = fcntl(sockFd, F_GETFL);
    if (opt < 0) {
        printf("fcntl(F_GETFL) fail.");
        exit(-1);
    }

    //设置新的flag,非阻塞
    opt |= O_NONBLOCK;
    if (fcntl(sockFd, F_SETFL, opt) < 0) {
        printf("fcntl(F_SETFL) fail.");
        exit(-1);
    }
}

int main() {

    /* 设置每个进程允许打开的最大文件数 */
    //当epoll监听过多描述符的时候，需要设置此项
    struct rlimit rt;
    rt.rlim_max = rt.rlim_cur = EPOLL_SIZE;
    if ( setrlimit( RLIMIT_NOFILE, &rt ) == -1 )
    {
        perror( "setrlimit" );
        exit( 1 );
    }
    
    int serverFd;

    //创建服务器fd
    serverFd = socket(AF_INET, SOCK_STREAM, 0);
    setnonblocking(serverFd);

    //创建epoll，并把serverFd放入监听队列
    int epFd = epoll_create(EPOLL_SIZE);
    struct epoll_event ev, evs[EVENT_ARR];
    ev.data.fd = serverFd;
    ev.events = EPOLLIN | EPOLLET;
    epoll_ctl(epFd, EPOLL_CTL_ADD, serverFd, &ev);

    //绑定服务器端口
    struct sockaddr_in serverAddr;
    socklen_t serverLen = sizeof(struct sockaddr_in);
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr.sin_port = htons(PORT);
    if (bind(serverFd, (struct sockaddr *) &serverAddr, serverLen)) {
        printf("bind() fail.\n");
        exit(-1);
    }

    //打开监听
    if (listen(serverFd, BACK_QUEUE)) {
        printf("Listen fail.\n");
        exit(-1);
    }

    //死循环处理
    int clientFd;
    struct sockaddr_in clientAddr;
    socklen_t clientLen;
    char buf[BUF_SIZE];
    
    while (1) 
    {
        //等待epoll事件的到来，最多取EVENT_ARR个事件
        int nfds = epoll_wait(epFd, evs, EVENT_ARR, -1);
        //处理事件
        for (int i = 0; i < nfds; i++) 
        {
            if (evs[i].data.fd == serverFd && evs[i].data.fd & EPOLLIN) 
            {
                //如果是serverFd,表明有新连接连入
                if ((clientFd = accept(serverFd,
                        (struct sockaddr *) &clientAddr, &clientLen)) < 0) 
                {
                    printf("accept fail.\n");
                }
                printf("Connect from %s:%d\n", inet_ntoa(clientAddr.sin_addr),
                        htons(clientAddr.sin_port));
                setnonblocking(clientFd);
                //注册accept()到的连接
                ev.data.fd = clientFd;
                ev.events = EPOLLIN | EPOLLET;
                epoll_ctl(epFd, EPOLL_CTL_ADD, clientFd, &ev);
            } 
            else if (evs[i].events & EPOLLIN) 
            {
                //如果不是serverFd,则是client的可读
                if ((clientFd = evs[i].data.fd) > 0) 
                {
                    //先进行试探性读取
                    int len = read(clientFd, buf, BUF_SIZE);
                    if (len > 0) 
                    {
                        //有数据可以读，Echo写入
                        do {
                            if (write(clientFd, buf, len) < 0) {
                                printf("write() fail.\n");
                            }
                            len = read(clientFd, buf, BUF_SIZE);
                        } while (len > 0);
                    } 
                    else if (len == 0) 
                    {
                        //出发了EPOLLIN事件，却没有可以读取的，表示断线
                        printf("Client closed at %d\n", clientFd);
                        epoll_ctl(epFd, EPOLL_CTL_DEL, clientFd, &ev);
                        close(clientFd);
                        evs[i].data.fd = -1;
                        break;
                    } 
                    else if (len == EAGAIN) {
                        continue;
                    } 
                    else 
                    {
                        //client读取出错
                        printf("read() fail.");
                    }
                }
            } 
            else 
            {
                printf("other event.\n");
            }
        }
    }

    return 0;
}


